package uk.ac.glasgow.demosocs.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import uk.ac.glasgow.demosocs.*;
import uk.ac.glasgow.demosocs.impl.election.*;
import uk.ac.glasgow.demosocs.users.*;

public class VotingSystemStub implements VotingSystem {

	// default election id counter
	private final int DEFAULT_ELECTION_ID = 1;

	// counter for election id
	private int mElectionIDCounter;

	//ElectionImpl storage facility
	ElectionStoreImpl mElectionStore;
	
	// current user guid
	private String mCurrentUserGUID;
	// system current status
	private String mCurrentElectionID;

	// collection of all voters in the system
	private Electorate mElectorate;

	/**
	 * constructor
	 */
	public VotingSystemStub() {

		// counter for election id
		mElectionIDCounter = DEFAULT_ELECTION_ID;

		mCurrentElectionID = null;

		// collections is the stub
		mElectionStore = new ElectionStoreImpl("Elections.data");

		// initialize collections
		UserTextReader mReader = new UserTextReader();
		// read electorate from the txt file
		mElectorate = mReader.readFromText();

	}

	@Override
	/**
	 * set the current election id to current election id
	 */
	public void setCurrentElection(String electionID) {
		mCurrentElectionID = electionID;
	}

	@Override
	/**
	 * get the current election object by eid
	 */
	public Election getCurrentElection() {
		return getElectionImplByID(mCurrentElectionID);
	}

	/**
	 * test if the user valid, if it is set to current user
	 */
	public boolean setCurrentUser(String GUID, String password) {
		// check the password valid or not
		if (mElectorate.matche(GUID, password)) {
			mCurrentUserGUID = GUID;
			return true;
		} else
			return false;
	}

	@Override
	public String getCurrentUserGUID() {
		return mCurrentUserGUID;
	}

	@Override
	/**
	 * return the system managed elections currently
	 */
	public Set<Election> getElections() {
		Set<Election> returnElections = new HashSet<Election>();
		// changeing the type of electiomimpl to election
		for (ElectionImpl e : mElectionStore.getTheElections()) {
			e.checkTime();
			returnElections.add(e);
		}
		return returnElections;
	}

	@Override
	/**
	 * return a set of elections that the current user has been nominated
	 */
	public Set<Election> getNominations() {
		Set<Election> pNominatedElections = new HashSet<Election>();

		// go through every election in the election collections
		for (ElectionImpl election : mElectionStore.getTheElections()) {

			// if the current user was nominated in the election
			if (election.isNomineeSetComtainsGUID(mCurrentUserGUID))
				// add the election to the return set
				pNominatedElections.add(election);
		}

		return pNominatedElections;
	}

	@Override
	/**
	 * create a election based on name, type and add it to the election collection
	 * return void
	 */
	public void createElection(String name, String typeID) {
		// check is the current user system admin
		if (mCurrentUserGUID != null) {
			if (mElectorate.getVoterByID(mCurrentUserGUID).getRole()
					.equals(Voter.ADMIN)) {

				// create the election
				ElectionImpl election = new ElectionImpl(name, typeID);
				String electionid = Integer.toString(mElectionIDCounter);
				// assigning eid
				election.setElectionID(electionid);
				// set the current election to newly created
				this.setCurrentElection(electionid);
				// counter +1
				mElectionIDCounter++;

				// add the new election to election store
				mElectionStore.addElectionImpl(election);

			} else
				System.err
						.println("Sorry, only system admin can create election.");
		}
	}

	@Override
	public void editElection(Date openNominations, Date start, Date end,
			String electorateID) {
		ElectionImpl election = getElectionImplByID(mCurrentElectionID);
		if (mCurrentUserGUID != null) {
			// check current selected election state
			// if the election is started we cannot modify the date
			if (!election.getElectionState().equals(ElectionState.NOT_STARTED)) {
				System.err.println("The election has already started!");
			} else {

				// check the given election times are valid
				if (!beforeAndEqual(openNominations, start)) {
					System.err
							.println("Election creation failed, Start election time must be equal or after to open nomination time.");
					// because the election time's error the newly created
					// election has been removed from the collection
					removeElection(mCurrentElectionID);

				} else if (!beforeAndEqual(start, end)) {
					System.err
							.println("Election creation failed, End election must be after or equal to start election time");
					// removed the election
					removeElection(mCurrentElectionID);

				} else {
					election.setNominationOpenDate(openNominations);
					election.setStart(start);
					election.setEnd(end);
				}
				// associate the election with the only electorate
				election.setElectorate(mElectorate);
				//updation the current time and changing the elections current statues
				election.checkTime();
				//save the newly edited election object to file
				mElectionStore.saveElectionsToFile();
			}
		} else
			System.err.println("You have not set a current user.");
	}

	@Override
	/**
	 * nominate a voter for the current selected election
	 * param string guid for the nominated people
	 */
	public void nominateCandidate(String GUID) {
		// check nominator and nominee
		ElectionImpl election = getElectionImplByID(mCurrentElectionID);

		if (mElectorate.containsGUID(GUID)
				&& mElectorate.containsGUID(mCurrentUserGUID)){
			// nomination process
			election.nominate(mCurrentUserGUID, GUID);
			//as the election nomination information has changed, the election is saved to file again
			mElectionStore.saveElectionsToFile();
		}
		else {
			System.err
					.println("The current user is not in the current election electorate.");
		}
	}

	@Override
	/**
	 * Accepts the nomination in the currently selected election for the
	 * currently selected user.
	 */
	public void acceptNomination() {
		ElectionImpl election = getElectionImplByID(mCurrentElectionID);
		// check does the user is a nominee for current election
		if (election.isNomineeSetComtainsGUID(mCurrentUserGUID)) {
			// accept nomination process
			election.nomineeAccept(mCurrentUserGUID);
			//save the election to file
			mElectionStore.saveElectionsToFile();
		} else
			System.err
					.println("Sorry, you are not a nominee for the current selected election.");
	}

	@Override
	/**
	 * cast a vote for the current voter in the current election to one of the candidate
	 */
	public void castVote(Vote vote) {
		//get the current election object
		ElectionImpl election = getElectionImplByID(mCurrentElectionID);
		
		// check current user
		if (mElectorate.containsGUID(mCurrentUserGUID)) {
			// checking current election is not null
			if (!election.equals(null)) {
				// voting process
				OneOfNVote nVote = (OneOfNVote) vote;
				election.vote(mCurrentUserGUID,
						nVote.getCandidateGUID());
				//save the election information to file
				mElectionStore.saveElectionsToFile();
			} else
				System.err
						.println("Please set the current election correctly.");
		} else
			System.err.println("Current user not correct!");
	}

	@Override
	public void publishResults() {
		ElectionImpl election = getElectionImplByID(mCurrentElectionID);
		election.checkTime();
		// update election status
		// checking current election
		if (!election.equals(null)) {
			// check current user is admin
			if (mElectorate.getVoterByID(mCurrentUserGUID).getRole()
					.equals(Voter.ADMIN)) {
				// get result
				ElectionResultImpl result = (ElectionResultImpl) election
						.getElectionResult();
				// do publish
				System.out.println(result.getResult());
			}
		}

	}

	/**
	 * compare two dates
	 * 
	 * @param first
	 *            -- the date supposed be earlier
	 * @param second
	 *            -- supposed to be late
	 * @return boolean , true if first is earlier
	 */
	public boolean beforeAndEqual(Date first, Date second) {
		return first.equals(second) || first.before(second);
	}

	/**
	 * find the electionimpl object based on id
	 * 
	 * @param electionID
	 * @return electionImpl
	 */
	public ElectionImpl getElectionImplByID(String electionID) {
		return mElectionStore.getElectionImpl(electionID);
	}

	/**
	 * remove the election object from the collection
	 * 
	 * @param electionID
	 */
	public void removeElection(String electionID) {
		//try to remove the election by id
		mElectionStore.removeElection(electionID);
		// test if the current election id is same as the removed id
		if (mCurrentElectionID.equals(electionID))
			mCurrentElectionID = null;
	}

	public void eraseAllElections(){
		mElectionStore.cleanElectionStoreAndCreateNewFile();
	}
}
